// #ifdef APP-PLUS || H5
// ----------------------------------------------------
// 这是 App 和 H5 环境下会编译进去的完整版 IM 服务
// ----------------------------------------------------
import {
	EMClient,
	EasemobSDK
} from '@/EaseIM/index.js'; // 引入EMClient实例和SDK
// import { getAgoraToken } from '@/request/api.js'; // 获取声网Token的接口
import Vue from 'vue'; // 用于创建事件总线
import {
	getQiniuToken
} from '@/request/api.js'; // 引入获取七牛云上传凭证的API
import store from '@/store'; // 引入Vuex store以获取配置

// 创建一个全局事件总线
const eventBus = new Vue();

let isInitialized = false;

// 辅助函数，确保conn对象存在
function getConn() {
	if (!EMClient) {
		console.error("EMClient 未初始化!");
		// 应该在App.vue中确保 EMClient 被正确实例化
	}
	return EMClient;
}

const imService = {
	eventBus, // 暴露事件总线

	// 初始化：主要是设置监听器
	init(configOptions = {}) { // configOptions 可以是 appKey 等，但我们已在EaseIM/index.js中配置
		if (isInitialized) {
			console.error("IM 服务已初始化.");
			return Promise.resolve("已初始化.");
		}
		const conn = getConn();
		if (!conn) return Promise.reject("EMClient 无法使用.");

		// 统一的事件处理器名字，避免重复添加
		const GLOBAL_EVENT_HANDLER_ID = "IM_SERVICE_GLOBAL_HANDLER";

		conn.removeEventHandler(GLOBAL_EVENT_HANDLER_ID); // 先移除，防止重复添加（如果init被意外多次调用）

		conn.addEventHandler(GLOBAL_EVENT_HANDLER_ID, {
			onOpened: (message) => {
				eventBus.$emit('im_opened', message);
			},
			onClosed: (message) => {
				eventBus.$emit('im_closed', message);
			},
			onTextMessage: (message) => {
				eventBus.$emit('im_message_received', {
					type: 'text',
					...message
				});
			},
			onImageMessage: (message) => {
				eventBus.$emit('im_message_received', {
					type: 'image',
					...message
				});
			},
			// 添加更多消息类型监听：onAudioMessage, onVideoMessage, onFileMessage, onCmdMessage, onCustomMessage 等
			onPresence: (message) => {
				eventBus.$emit('im_presence_changed', message);
			},
			onContactInvited: (msg) => { // 好友申请
				eventBus.$emit('im_contact_invited', msg);
			},
			onContactAgreed: (msg) => { // 同意好友申请
				eventBus.$emit('im_contact_agreed', msg);
			},
			onContactRefuse: (msg) => { // 拒绝好友申请
				eventBus.$emit('im_contact_refused', msg);
			},
			onContactDeleted: (msg) => { // 被删除好友
				eventBus.$emit('im_contact_deleted', msg);
			},
			// 收到通话邀请的监听
			onInviteMessage: (message) => {
				// 使用事件总线通知全局，例如 App.vue 来显示来电界面
				eventBus.$emit('im_incoming_call', message);
			},

			// 通话状态变化监听
			onCallStateChange: (callInfo) => {
				// callInfo.state: 'accepted', 'rejected', 'busy', 'ended'
				// callInfo.ext: 包含自定义信息
				eventBus.$emit('im_call_state_change', callInfo);
			},
			// ...其他你需要处理的事件，如群组事件、错误事件等
			onError: (error) => {
				console.error("[imService] IM错误:", error);
				eventBus.$emit('im_error', error);
			}
		});

		isInitialized = true;
		console.log("[imService] 已使用事件侦听器初始化.");
		return Promise.resolve("已配置IM服务侦听器.");
	},

	// 登录
	login(username, tokenOrPassword) {
		const conn = getConn();
		if (!conn) return Promise.reject("EMClient 无法使用的.");

		return new Promise((resolve, reject) => {
			const options = {
				user: username + '',
				// 根据你的后端和环信配置，决定是使用密码(pwd)还是token(accessToken)
				// pwd: tokenOrPassword, // 如果使用密码登录
				accessToken: tokenOrPassword, // 如果使用token登录
			};
			conn.open(options)
				.then((res) => {
					console.log('环信登录成功');
					resolve(res);
				})
				.catch((err) => {
					console.error("[imService] 登录失败:", err);
					reject(err);
				});
		});
	},

	// 登出
	logout() {
		const conn = getConn();
		if (!conn) return Promise.resolve("EMClient 无法使用的 或未连接.");

		if (conn.isOpened()) {
			conn.close();
		} else {
			console.warn("[imService] 已注销或未连接.");
		}
		return Promise.resolve("已处理注销过程."); // close()是同步的，onClosed事件会异步触发
	},

	// 发送文本消息
	sendTextMessage(to, content, chatType = 'singleChat', ext = {}, quoteInfo = null) { // 新增 quoteInfo 参数
		const conn = getConn();
		if (!conn || !conn.isOpened()) {
			return Promise.reject({
				errMsg: "未连接到IM服务器."
			});
		}

		const localMsgId = conn.getUniqueId(); // SDK 生成本地消息ID
		const currentTimestamp = Date.now(); // 获取当前时间戳

		let messageBody = {
			type: 'txt',
			msg: content,
		};

		// 如果有引用信息，添加到 ext 中 (环信SDK通常通过ext传递自定义数据)
		if (quoteInfo && quoteInfo.name && quoteInfo.content) {
			ext.quote = { // 自定义一个 quote 结构
				msgId: quoteInfo.id || '', // 被引用的消息ID (如果有)
				senderName: quoteInfo.name,
				quoteContent: quoteInfo.content,
				msgType: quoteInfo.type || 'txt' // 被引用的消息类型
			};
		}

		const msgToSend = EasemobSDK.message.create({
			id: localMsgId, // 使用本地生成的消息ID
			to: to,
			from: conn.user,
			type: 'txt', // 消息类型
			msg: content,
			body: messageBody, // 消息体，对于文本消息，可以直接是字符串，或者用对象
			chatType: chatType,
			ext: ext,
			time: currentTimestamp // 记录发送时的时间戳
		});

		// 为了UI即时反馈，我们可以在发送前就构造一个本地消息对象
		const localMessageForUI = {
			id: localMsgId,
			to: to,
			from: conn.user,
			chatType: chatType,
			msg: content,
			contentType: 'text',
			type: 1, // 1 表示自己发送
			avatar: conn.context?.jid?.name || conn.user, // 发送者头像 (假设从conn或全局取)
			userId: conn.user,
			name: conn.user, // 发送者昵称
			time: this.formatDisplayTime(currentTimestamp, null), // 格式化显示时间
			timestamp: currentTimestamp, // 原始时间戳
			status: 'sending', // 发送状态
			quoteInfo: quoteInfo ? { // 确保 quoteInfo 结构一致性
				id: quoteInfo.id,
				name: quoteInfo.name,
				content: quoteInfo.content,
				type: quoteInfo.type
			} : null
		};

		// 立即返回这个本地消息对象，让UI可以先渲染
		// 然后异步发送实际消息
		const sendPromise = conn.send(msgToSend)
			.then((res) => {
				// res.serverMsgId 是服务器生成的消息ID
				return {
					localId: localMsgId,
					serverId: res.serverMsgId,
					message: msgToSend, // 返回创建的SDK消息对象
					status: 'sent'
				};
			})
			.catch((err) => {
				console.error("[imService] 发送文本消息失败:", err);
				throw {
					...err,
					localId: localMsgId,
					status: 'failed'
				}; // 抛出包含本地ID的错误
			});

		return {
			localMessageForUI,
			sendPromise
		}; // 返回本地消息和发送的Promise
	},

	// 辅助方法：格式化显示时间 (可以从你的 history-msg.js 借鉴或直接使用)
	formatDisplayTime(timestamp, prevTimestamp) {
		if (!timestamp) return '';
		const now = new Date();
		const msgDate = new Date(timestamp);
		const diffMinutes = (now.getTime() - msgDate.getTime()) / (1000 * 60);

		// 如果与上一条消息的时间间隔很短（例如5分钟内），则不显示时间
		if (prevTimestamp) {
			const prevMsgDate = new Date(prevTimestamp);
			const intervalMinutes = (msgDate.getTime() - prevMsgDate.getTime()) / (1000 * 60);
			if (intervalMinutes < 5) { // 5分钟内不重复显示时间
				return null; // 返回null表示不显示这条消息的时间
			}
		}

		const hours = msgDate.getHours().toString().padStart(2, '0');
		const minutes = msgDate.getMinutes().toString().padStart(2, '0');

		if (msgDate.toDateString() === now.toDateString()) { // 今天
			return `${hours}:${minutes}`;
		} else if (now.getFullYear() === msgDate.getFullYear()) { // 今年
			const month = (msgDate.getMonth() + 1).toString().padStart(2, '0');
			const day = msgDate.getDate().toString().padStart(2, '0');
			return `${month}-${day} ${hours}:${minutes}`;
		} else { // 更早
			const year = msgDate.getFullYear();
			const month = (msgDate.getMonth() + 1).toString().padStart(2, '0');
			const day = msgDate.getDate().toString().padStart(2, '0');
			return `${year}-${month}-${day} ${hours}:${minutes}`;
		}
	},

	// 发送图片消息 (简化版，实际需要结合 uni.chooseImage 和 uni.uploadFile/SDK上传)
	/**
	 * 发送图片消息 (与 sendTextMessage 逻辑对齐)
	 * @param {string} to - 接收者ID
	 * @param {string} filePath - 图片的本地临时路径
	 * @param {string} chatType - 'singleChat' 或 'groupChat'
	 * @param {object} ext - 扩展消息
	 * @returns {{localMessageForUI: object, sendPromise: Promise<any>}} - 返回一个包含本地UI消息和发送Promise的对象
	 */
	sendImageMessage(to, filePath, chatType = 'singleChat', ext = {}) {
		const conn = getConn();
		if (!conn || !conn.isOpened()) {
			const error = {
				errMsg: "未连接到IM服务器。"
			};
			// 返回一个立即被拒绝的Promise，保持结构一致性
			return {
				localMessageForUI: null,
				sendPromise: Promise.reject(error)
			};
		}

		const localMsgId = conn.getUniqueId();
		const currentTimestamp = Date.now();

		// 1. 创建用于UI即时反馈的本地消息对象
		const localMessageForUI = {
			to: to,
			from: conn.user,
			id: localMsgId,
			localId: localMsgId, // 确保有localId
			content: filePath, // 初始内容为本地路径
			contentType: 'img',
			type: 1, // 1 表示自己发送
			status: 'uploading', // 初始状态为上传中
			timestamp: currentTimestamp,
			avatar: store.state.userInfo.avatar,
			userId: store.state.im.imUserId,
			name: store.state.userInfo.nickName || "我",
			// 确保其他必要字段存在
		};

		// 2. 异步处理上传和发送，封装在 sendPromise 中
		const sendPromise = new Promise(async (resolve, reject) => {
			try {
				// 2.1 获取七牛云上传凭证
				const filename =
					`im_image_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`;
				const tokenRes = await getQiniuToken({
					originalFilename: filename,
					purpose: 'chat_image'
				});
				if (!tokenRes.data?.data?.token) throw new Error('获取上传凭证失败');
				const {
					token: qiniuToken,
					key: qiniuKey
				} = tokenRes.data.data;

				// 2.2 上传文件到七牛云
				const imageUrlOnQiniu = await new Promise((resolveUpload, rejectUpload) => {
					uni.uploadFile({
						url: store.state.qiNiuUploadUrl,
						filePath: filePath,
						name: 'file',
						formData: {
							token: qiniuToken,
							key: qiniuKey
						},
						success: (res) => {
							try {
								const data = JSON.parse(res.data);
								resolveUpload(
									`${store.state.ossBaseUrl}/${data.key}`);
							} catch (e) {
								rejectUpload(new Error('上传响应解析错误'));
							}
						},
						fail: (err) => rejectUpload(new Error('上传到七牛云失败'))
					});
				});

				// 上传成功，更新UI消息状态为“发送中”，并替换内容为网络URL
				this.eventBus.$emit('im_message_status_updated', {
					conversationId: to,
					localId: localMsgId,
					status: 'sending',
					newContent: imageUrlOnQiniu,
				});

				// 2.3 创建环信图片消息
				const msgToSend = EasemobSDK.message.create({
					type: 'img',
					to: to,
					from: conn.user,
					chatType: chatType,
					url: imageUrlOnQiniu,
					ext: ext,
					time: currentTimestamp,
				});

				// 2.4 发送环信消息
				// 注意：我们直接 await conn.send()，这是基于它返回Promise的假设。
				// 如果它不返回Promise，则需要像上一个回答那样使用回调函数。
				// 现代的环信SDK版本，`conn.send()` 确实返回一个Promise。
				const res = await conn.send(msgToSend);

				// Promise成功完成，返回结果
				resolve({
					localId: localMsgId,
					serverId: res.serverMsgId,
					message: msgToSend,
					status: 'sent'
				});

			} catch (error) {
				console.error("[imService] 发送图片消息流程中出现错误:", error);
				// 任何步骤失败，都拒绝Promise，并附带本地ID和失败状态
				reject({
					...error,
					localId: localMsgId,
					status: 'failed'
				});
			}
		});

		// 3. 立即返回包含本地UI消息和Promise的对象
		return {
			localMessageForUI,
			sendPromise
		};
	},
	/**
	 * 发送视频消息
	 * @param {string} to - 接收者ID
	 * @param {string} filePath - 视频的本地临时路径
	 * @param {number} duration - 视频时长 (秒)
	 * @param {string} chatType - 'singleChat' 或 'groupChat'
	 * @param {object} ext - 扩展消息
	 * @returns {{localMessageForUI: object, sendPromise: Promise<any>}}
	 */
	sendVideoMessage(to, filePath, duration, chatType = 'singleChat', ext = {}) {
		const conn = getConn();
		if (!conn || !conn.isOpened()) {
			return {
				localMessageForUI: null,
				sendPromise: Promise.reject({
					errMsg: "未连接到IM服务器。"
				})
			};
		}

		const localMsgId = conn.getUniqueId();
		const currentTimestamp = Date.now();

		// 1. 创建用于UI即时反馈的本地消息对象
		const localMessageForUI = {
			to: to,
			from: conn.user,
			id: localMsgId,
			localId: localMsgId,
			content: filePath, // 初始内容为本地路径
			contentType: 'video',
			type: 1,
			status: 'uploading',
			timestamp: currentTimestamp,
			duration: duration,
			avatar: store.state.userInfo.avatar,
			userId: store.state.im.imUserId,
			name: store.state.userInfo.nickName || "我",
		};

		// 2. 异步处理上传和发送
		const sendPromise = new Promise(async (resolve, reject) => {
			try {
				// 2.1 获取七牛云上传凭证
				const filename =
					`im_video_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.mp4`;
				const tokenRes = await getQiniuToken({
					originalFilename: filename,
					purpose: 'chat_video'
				});
				if (!tokenRes.data?.data?.token) throw new Error('获取上传凭证失败');
				const {
					token: qiniuToken,
					key: qiniuKey
				} = tokenRes.data.data;

				// 2.2 上传文件到七牛云
				const videoUrlOnQiniu = await new Promise((resolveUpload, rejectUpload) => {
					uni.uploadFile({
						url: store.state.qiNiuUploadUrl,
						filePath: filePath,
						name: 'file',
						formData: {
							token: qiniuToken,
							key: qiniuKey
						},
						success: (res) => {
							try {
								const data = JSON.parse(res.data);
								resolveUpload(
									`${store.state.ossBaseUrl}/${data.key}`);
							} catch (e) {
								rejectUpload(new Error('上传响应解析错误'));
							}
						},
						fail: (err) => rejectUpload(new Error('上传到七牛云失败'))
					});
				});

				// 2.3 创建环信视频消息体
				// 注意：请根据你的环信SDK版本确认视频消息体的确切结构
				// 常见字段包括 url, filename, secret, file_length, thumb(缩略图URL), duration
				const msgBody = {
					type: 'video',
					url: videoUrlOnQiniu,
					filename: filename,
					duration: Math.round(duration), // 时长应为整数
					// 如果需要，还可以获取文件大小和缩略图
					// file_length: ..., 
					// thumb: '缩略图的URL',
					// secret: '你的后端生成的secret'
				};

				// 2.4 创建环信消息
				const msgToSend = EasemobSDK.message.create({
					type: 'video',
					to: to,
					from: conn.user,
					chatType: chatType,
					body: msgBody,
					ext: ext,
					time: currentTimestamp,
				});

				// 2.5 发送环信消息
				const res = await conn.send(msgToSend);

				// 返回结果，包含网络URL，以便UI可以更新
				resolve({
					localId: localMsgId,
					serverId: res.serverMsgId,
					message: msgToSend,
					status: 'sent',
					url: videoUrlOnQiniu, // 附加网络URL
				});

			} catch (error) {
				console.error("[imService] 发送视频消息流程中出现错误:", error);
				reject({
					...error,
					localId: localMsgId,
					status: 'failed'
				});
			}
		});

		return {
			localMessageForUI,
			sendPromise
		};
	},

	/**
	 * 发送语音消息
	 * @param {string} to - 接收者ID
	 * @param {string} filePath - 语音的本地临时路径
	 * @param {number} duration - 语音时长 (秒)
	 * @param {string} chatType - 'singleChat' 或 'groupChat'
	 * @param {object} ext - 扩展消息
	 * @returns {{localMessageForUI: object, sendPromise: Promise<any>}}
	 */
	sendVoiceMessage(to, filePath, duration, chatType = 'singleChat', ext = {}) {
		const conn = getConn();
		if (!conn || !conn.isOpened()) {
			return {
				localMessageForUI: null,
				sendPromise: Promise.reject({
					errMsg: "未连接到IM服务器。"
				})
			};
		}

		const localMsgId = conn.getUniqueId();
		const currentTimestamp = Date.now();

		const localMessageForUI = {
			id: localMsgId,
			localId: localMsgId,
			content: filePath,
			contentType: 'audio',
			to: to,
			type: 1,
			status: 'uploading',
			timestamp: currentTimestamp,
			duration: duration,
			avatar: store.state.userInfo.avatar,
			userId: store.state.im.imUserId,
			name: store.state.userInfo.nickName,
		};

		const sendPromise = new Promise(async (resolve, reject) => {
			try {
				const filename = `im_audio_${Date.now()}.mp3`;
				const tokenRes = await getQiniuToken({
					originalFilename: filename,
					purpose: 'chat_audio'
				});
				if (!tokenRes.data?.data?.token) throw new Error('获取上传凭证失败');
				const {
					token: qiniuToken,
					key: qiniuKey
				} = tokenRes.data.data;

				const audioUrlOnQiniu = await new Promise((resolveUpload, rejectUpload) => {
					uni.uploadFile({
						url: store.state.qiNiuUploadUrl,
						filePath: filePath,
						name: 'file',
						formData: {
							token: qiniuToken,
							key: qiniuKey
						},
						success: (res) => {
							const data = JSON.parse(res.data);
							resolveUpload(`${store.state.ossBaseUrl}/${data.key}`);
						},
						fail: (err) => rejectUpload(new Error('上传到七牛云失败'))
					});
				});

				const msgBody = {
					type: 'audio',
					url: audioUrlOnQiniu,
					filename: filename,
					length: duration, // 环信SDK V3版本用 `length` 表示时长
				};

				const msgToSend = EasemobSDK.message.create({
					type: 'audio',
					to: to,
					from: conn.user,
					chatType: chatType,
					body: msgBody,
					ext: ext,
					time: currentTimestamp,
				});

				const res = await conn.send(msgToSend);

				resolve({
					localId: localMsgId,
					serverId: res.serverMsgId,
					message: msgToSend,
					status: 'sent',
					url: audioUrlOnQiniu,
				});

			} catch (error) {
				console.error("[imService] 发送语音消息流程出错:", error);
				reject({
					...error,
					localId: localMsgId,
					status: 'failed'
				});
			}
		});

		return {
			localMessageForUI,
			sendPromise
		};
	},
	// 获取会话列表 (JS SDK 通常通过监听消息来实时维护，或通过REST API拉取)
	async getConversationsFromSDK() {
		const conn = getConn();
		if (!conn || !conn.isOpened()) {
			return Promise.resolve([]); // 未连接则返回空
		}
		// JS SDK 的 getConversationList 方法（如果有）通常返回的是内存中的会话
		// 首次进入应用或需要同步时，更可靠的方式是调用你后端封装的REST API
		// 或者 SDK 会在连接成功后自动同步一部分会话到本地 (例如通过事件)
		// 这里假设SDK有一个同步方法（具体API名需查文档）
		// return conn.getConversationList ? conn.getConversationList() : Promise.resolve([]);
		console.warn("[imService] getConversionsFromSDK:JS SDK通常通过响应式或REST管理对话。这是一个占位符.");
		const {
			data
		} = await conn.getServerConversations({
			pageSize: 10,
			cursor: ''
		})
		return Promise.resolve(data.conversations || []); // 尝试获取
	},

	// 获取好友列表 (通常通过 REST API 或 SDK 同步方法)
	getContacts() {
		const conn = getConn();
		if (!conn || !conn.isOpened()) {
			return Promise.resolve([]);
		}
		return conn.getContacts?.() || Promise.resolve([]);
	},

	// 标记会话已读
	markConversationAsRead(conversationId, chatType = 'singleChat') {
		const conn = getConn();
		if (!conn || !conn.isOpened()) return;
		// SDK 通常有ack消息的方法
		conn.send({
			type: 'read',
			to: conversationId, // 如果是单聊，这是对方用户ID；群聊是群ID
			chatType: chatType,
			// ackId: '需要标记为已读的最后一条消息的ID' // 可选，看SDK是否支持批量已读
		});
		// 或者，如果SDK有专门的会话管理API：
		// conn.conversationManager?.clearUnread?.(conversationId, chatType);
	},
	/**
	 * 从环信服务器获取历史消息
	 * @param {string} conversationId - 会话ID (对方用户ID或群ID)
	 * @param {string} chatType - 'singleChat' 或 'groupChat'
	 * @param {string|null} startMsgId - 开始拉取的消息ID (服务器ID)，用于分页。第一次为 null。
	 * @param {number} count - 拉取数量
	 * @returns {Promise<{messages: Array, cursor: string|null, hasMore: boolean}>}
	 */
	fetchHistoryMessagesFromServer(conversationId, chatType, startMsgId = null, count = 15) {
		const conn = getConn();
		if (!conn || !conn.isOpened()) {
			return Promise.reject({
				errMsg: "未连接到IM服务器."
			});
		}

		// **请查阅你所用 SDK 版本的确切 API 名称和参数**
		// 以下是基于通用 Web SDK 模式的示例，方法名可能是 fetchHistoryMessages 或类似
		// 参数名也可能不同，如 targetId, conversationType, cursor, pageSize, searchDirection

		const options = {
			targetId: conversationId, // 目标ID
			chatType: chatType, // 会话类型
			cursor: startMsgId, // 开始的消息ID (服务器ID)，用于分页
			count: count, // 拉取条数
			searchDirection: 'up', // 拉取方向: 'up' (更早的消息), 'down' (更新的消息)
			// SDK 可能还有其他参数，如 msgTypes (过滤消息类型)
		};


		// 假设SDK方法是 EMClient.chatManager.fetchHistoryMessages
		// 如果你的 SDK 结构不同，例如 EMClient.getHistoryMessages，请相应修改
		if (conn && typeof conn.getHistoryMessages === 'function') {
			return conn.getHistoryMessages(options)
				.then(result => {
					// SDK 返回的 result 结构需要根据文档确定
					// 通常包含:
					// result.messages: 消息数组
					// result.cursor: 下一次拉取使用的 cursor (可能是最早一条消息的ID)
					// result.hasMore (或者通过判断 messages 数量是否等于 count)
					return {
						messages: result.messages || [],
						cursor: result.cursor || (result.messages && result.messages.length > 0 ? result
							.messages[0].id : null), // 通常返回最早一条的ID作为下次的cursor
						hasMore: result.messages && result.messages.length === count // 一个简单的判断是否有更多的方式
					};
				})
				.catch(err => {
					console.error("[imService] 获取历史消息失败:", err);
					throw err;
				});
		} else {
			console.error("[imService] SDK不支持fetchHistoryMessages或chatManager不可用.");
			return Promise.reject({
				errMsg: "找不到用于获取历史记录的SDK方法."
			});
		}
	},
	/**
	 * 发起音视频通话
	 * @param {string} calleeId - 被叫方ID
	 * @param {string} callType - 'voice' 或 'video'
	 * @returns {Promise<object>} - 返回导航到通话页面所需的参数
	 */
	async makeCall(calleeId, callType) {
		const conn = getConn();
		if (!conn || !conn.isOpened()) {
			throw new Error("IM未连接");
		}

		// 1. 生成唯一的Agora频道名
		const channelName = `call_${conn.user}_${calleeId}_${Date.now()}`;

		// 2. 从你的业务后端获取Agora Token
		let agoraToken = '';
		try {
			const tokenRes = await getAgoraToken({
				channelName: channelName
			});
			if (tokenRes.data && tokenRes.data.token) {
				agoraToken = tokenRes.data.token;
			} else {
				throw new Error('获取通话凭证失败');
			}
		} catch (error) {
			console.error("获取Agora Token失败:", error);
			throw new Error('获取通话凭证失败');
		}

		// 3. 构建呼叫选项
		const callOption = {
			callee: calleeId,
			callType: callType === 'video' ? 0 : 1, // 0: 视频, 1: 语音
			ext: {
				// 在这里传递自定义信息，最重要的是Agora频道信息
				channelName: channelName,
				agoraToken: agoraToken,
				// 还可以传递其他信息，如发起方头像、昵称等
				callerAvatar: store.state.userInfo.avatar,
				callerNickname: store.state.userInfo.nickName
			}
		};

		try {
			// 4. 使用环信SDK发起呼叫
			if (callType === 'video') {
				await conn.call.makeVideoCall(callOption);
			} else {
				await conn.call.makeVoiceCall(callOption);
			}

			console.log(`[imService] ${callType} 呼叫邀请已发送给 ${calleeId}`);

			// 5. 返回导航到通话页面所需的所有参数
			return {
				isCaller: true,
				callType: callType,
				channelName: channelName,
				agoraToken: agoraToken,
				targetUser: {
					id: calleeId,
					// TODO: 从好友列表或API获取被叫方信息
					name: calleeId,
					avatar: ''
				}
			};
		} catch (error) {
			console.error(`[imService] 发起 ${callType} 呼叫失败:`, error);
			// 根据错误类型给出更友好的提示
			if (error.type === 10408) {
				throw new Error("对方不在线");
			}
			throw new Error("呼叫失败，请稍后重试");
		}
	},

	/**
	 * 接受通话邀请
	 * @param {object} inviteMessage - onInviteMessage 收到的消息对象
	 */
	answerCall(inviteMessage) {
		const conn = getConn();
		if (!conn || !conn.isOpened()) return;
		conn.call.accept(inviteMessage);
		console.log('[imService] 已发送接受邀请信令');
	},

	/**
	 * 拒绝通话邀请
	 * @param {object} inviteMessage - onInviteMessage 收到的消息对象
	 */
	rejectCall(inviteMessage) {
		const conn = getConn();
		if (!conn || !conn.isOpened()) return;
		conn.call.reject(inviteMessage);
		console.log('[imService] 已发送拒绝邀请信令');
	},

	/**
	 * 挂断通话
	 * @param {string} calleeId - 通话对方的ID
	 */
	hangupCall(calleeId) {
		const conn = getConn();
		if (!conn || !conn.isOpened()) return;
		conn.call.endCall({
			to: calleeId
		});
		console.log(`[imService] 已发送挂断信令给 ${calleeId}`);
	},
	isLoggedIn() {
		const conn = getConn();
		return conn && conn.isOpened();
	},
};

export default imService;

// #endif






// #ifndef APP-PLUS || H5
// =========================================================================
//  此部分代码仅在非 App 和 H5 平台（如微信小程序）编译时生效
//  提供一个与完整版结构相同的 “空壳” IM 服务，
//  以避免在这些平台上因缺少 IM 功能而导致的其他代码报错。
// =========================================================================

import Vue from 'vue';

// 保持一个空的事件总线，让其他模块可以安全地调用 $on, $off, $emit 而不报错
const eventBus = new Vue();

const imServiceMock = {
	/**
	 * @property {Vue} eventBus - 一个空的事件总线实例，用于解耦。
	 */
	eventBus,

	/**
	 * 初始化 IM 服务。在小程序中，此操作为空，并直接返回成功的 Promise。
	 * @returns {Promise<string>}
	 */
	init() {
		console.warn('IM Service is disabled in this platform. `init` called.');
		return Promise.resolve("IM Service Mock: Initialized.");
	},

	/**
	 * 登录。在小程序中，此操作会直接失败。
	 * @param {string} username - 用户名
	 * @param {string} tokenOrPassword - Token 或密码
	 * @returns {Promise<never>} - 总是返回一个被拒绝的 Promise
	 */
	login(username, tokenOrPassword) {
		console.warn('IM Service is disabled. `login` call ignored.');
		return Promise.reject({
			errMsg: "IM功能在此平台不可用"
		});
	},

	/**
	 * 登出。在小程序中，此操作为空，并直接返回成功的 Promise。
	 * @returns {Promise<void>}
	 */
	logout() {
		console.warn('IM Service is disabled. `logout` call ignored.');
		return Promise.resolve();
	},

	/**
	 * 发送文本消息。在小程序中，此操作会直接失败。
	 * @returns {{localMessageForUI: null, sendPromise: Promise<never>}}
	 */
	sendTextMessage() {
		console.warn('IM Service is disabled. `sendTextMessage` call ignored.');
		return {
			localMessageForUI: null,
			sendPromise: Promise.reject({
				errMsg: "IM功能在此平台不可用"
			})
		};
	},

	/**
	 * 发送图片消息。在小程序中，此操作会直接失败。
	 * @returns {{localMessageForUI: null, sendPromise: Promise<never>}}
	 */
	sendImageMessage() {
		console.warn('IM Service is disabled. `sendImageMessage` call ignored.');
		return {
			localMessageForUI: null,
			sendPromise: Promise.reject({
				errMsg: "IM功能在此平台不可用"
			})
		};
	},

	/**
	 * 发送视频消息。在小程序中，此操作会直接失败。
	 * @returns {{localMessageForUI: null, sendPromise: Promise<never>}}
	 */
	sendVideoMessage() {
		console.warn('IM Service is disabled. `sendVideoMessage` call ignored.');
		return {
			localMessageForUI: null,
			sendPromise: Promise.reject({
				errMsg: "IM功能在此平台不可用"
			})
		};
	},

	/**
	 * 发送语音消息。在小程序中，此操作会直接失败。
	 * @returns {{localMessageForUI: null, sendPromise: Promise<never>}}
	 */
	sendVoiceMessage() {
		console.warn('IM Service is disabled. `sendVoiceMessage` call ignored.');
		return {
			localMessageForUI: null,
			sendPromise: Promise.reject({
				errMsg: "IM功能在此平台不可用"
			})
		};
	},

	/**
	 * 发起音视频通话。在小程序中，此操作会直接失败。
	 * @returns {Promise<never>}
	 */
	makeCall() {
		console.warn('IM Service is disabled. `makeCall` call ignored.');
		return Promise.reject({
			errMsg: "音视频通话功能在此平台不可用"
		});
	},

	/**
	 * 获取会话列表。在小程序中，返回一个空数组。
	 * @returns {Promise<Array>}
	 */
	getConversationsFromSDK() {
		console.warn('IM Service is disabled. `getConversationsFromSDK` call ignored, returning empty array.');
		return Promise.resolve([]);
	},

	/**
	 * 标记会话已读。在小程序中，此操作为空。
	 */
	markConversationAsRead(conversationId, lastMessageId) {
		console.warn('IM Service is disabled. `markConversationAsRead` call ignored.');
		// 无需做任何事
	},

	/**
	 * 检查是否已登录。在小程序中，始终返回 false。
	 * @returns {boolean}
	 */
	isLoggedIn() {
		return false;
	},

	/**
	 * 格式化显示时间。提供一个简单的占位实现，或返回空字符串。
	 * @param {number} timestamp 
	 * @param {number} prevTimestamp 
	 * @returns {string|null}
	 */
	formatDisplayTime(timestamp, prevTimestamp) {
		// 可以返回一个空字符串或一个简单的格式，以防止UI上出现 'undefined'
		if (!timestamp) return '';
		const d = new Date(timestamp);
		return `${d.getHours()}:${d.getMinutes().toString().padStart(2, '0')}`;
	},

	/**
	 * 接受通话邀请。在小程序中为空操作。
	 */
	answerCall() {
		console.warn('IM Service is disabled. `answerCall` call ignored.');
	},

	/**
	 * 拒绝通话邀请。在小程序中为空操作。
	 */
	rejectCall() {
		console.warn('IM Service is disabled. `rejectCall` call ignored.');
	},

	/**
	 * 挂断通话。在小程序中为空操作。
	 */
	hangupCall() {
		console.warn('IM Service is disabled. `hangupCall` call ignored.');
	},

	/**
	 * 获取历史消息。在小程序中返回一个空结果。
	 * @returns {Promise<{messages: Array, cursor: null, hasMore: boolean}>}
	 */
	fetchHistoryMessagesFromServer() {
		console.warn('IM Service is disabled. `fetchHistoryMessagesFromServer` call ignored.');
		return Promise.resolve({
			messages: [],
			cursor: null,
			hasMore: false,
		});
	},

	/**
	 * 获取好友列表。在小程序中返回一个空数组。
	 * @returns {Promise<Array>}
	 */
	getContacts() {
		console.warn('IM Service is disabled. `getContacts` call ignored.');
		return Promise.resolve([]);
	}
};

// 默认导出这个空壳对象
export default imServiceMock;
// #endif